#include "../../includes/scripts/cjsmanager.h"
#include "../../includes/QsLog/QsLog.h"
#include "../../includes/common/common.h"

#include <QFileInfo>
#include <QDir>
#include <QTextCodec>
#include <qDebug>
#include <QQmlEngine>
#include <QJsonArray>
#include <QLibrary>

#define IDD_RESOURCE_UPDATE  0
#define IDD_RESOURCE_DELETE  1
#define IDD_RESOURCE_ADD     2

typedef void (*reg_fun)(CJsManager *g_JsManager);

CJsManager::CJsManager(QWidget *parent)
    : m_isLoadScript(true),
      m_IsEnableAuthManager(false)
{
    QTextCodec *codec = QTextCodec::codecForName("UTF-8");//或者"GBK",不分大小写
    QTextCodec::setCodecForLocale(codec);

    //m_JSEngine.setParent(this);

    m_SystemWorkingPathTimer = new QTimer(this);
    m_SystemWorkingPathTimer->setSingleShot(true);

    m_WebSocketServer.SetNetworkFrameManager(this);

    connect(m_SystemWorkingPathTimer, SIGNAL(timeout()), this, SLOT(WorkingdirectoryChanged()));
    connect(&m_pSystemWorkingPathWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(WorkingdirectoryChange(QString)));
}

CJsManager::~CJsManager()
{
    clean_system();

    delete m_SystemWorkingPathTimer;
    m_SystemWorkingPathTimer = NULL;
}

void CJsManager::WorkingdirectoryChange(const QString &path)
{
    m_SystemWorkingPathTimer->start(3000);
}

/**
 * @brief CJsManager::init_system 初始化系统
 * @param workingpath 系统工作目录
 * @param regdllpath 插件目录
 * @param serverport 要打开的服务器端口
 *
 * @return 如果系统初始化成功返回真，否则返回假
 */
bool CJsManager::init_system(QString workingpath,QString regdllpath,int serverport)
{
    if(workingpath.isEmpty())
        return false;

    // 设置工作目录
    this->setCurrentWorkingPath(workingpath);

    // 设置插件目录
    this->setRegisterDllPath(regdllpath);

    // 是否启用网络
    if(serverport > 0)
        m_WebSocketServer.OpenServer(serverport);

    RegisterNewObject("sys",this,true);

    // 导入脚本文件
    WorkingdirectoryChanged();

    // 导入注册的插件
    reg_all2jss();

    QString logmsg = QString::fromUtf8("JS脚本系统初始成功,工作目录:") +
            workingpath+
            QString::asprintf("websocket server:%d",serverport);
    Log(logmsg,"info");

    return true;
}

/**
 * @brief CJsManager::addSysManager 添加一个管理员账号
 * @param name 管理员账号名
 * @param pwd 管理员密码
 */
void CJsManager::addSysManager(QString name,QString pwd)
{
    if(name.isEmpty() || pwd.isEmpty())
        return;

    m_AuthManager[name] = pwd;
}

/**
 * @brief CJsManager::delSysManager 删除指定的管理员
 * @param name 要删除的管理员
 */
void CJsManager::delSysManager(QString name)
{
    if(name.isEmpty())
        return;

    QMap<QString,QString>::iterator iter = m_AuthManager.find(name);
    if(iter != m_AuthManager.end())
        m_AuthManager.erase(iter);
}

/**
 * @brief CJsManager::clearSysManagers 清除所有的管理员
 */
void CJsManager::clearSysManagers(void)
{
    m_AuthManager.clear();
}

/**
 * @brief CJsManager::RegisterNewObject 注册一个新的对象
 * @param objName 新对象的名称
 * @param newobject 要注册的对象
 * @param isCppOwnership 如果设置成真表示归c++所有，如果设置成假表示归JS所有，JS在退出时会自动删除这个对象
 * @return 返回注册的值
 */
QJSValue CJsManager::RegisterNewObject(QString objName,QObject *newobject,bool isCppOwnership)
{
    if(isCppOwnership)
        QQmlEngine::setObjectOwnership(newobject, QQmlEngine::CppOwnership);

    QJSValue returnObj = m_JSEngine.newQObject(newobject);
    m_JSEngine.globalObject().setProperty(objName, returnObj);

    return returnObj;
}

/**
 * @brief CJsManager::clean_system 卸载系统
 */
void CJsManager::clean_system(void)
{
    m_WebSocketServer.CloseServer();
    clearSysManagers();
}

/**
 * @brief CJsManager::SendLog 发送日志信息
 * @param msg 要发送的日志信息
 * @param type 日志类型
 * @param isPrint 是否打印
 * @param isSend 是否网络发送
 * @param isLog 是否本地记录
 * @param isProg 是否要显示代码信息，初始不显示
 */
void CJsManager::Log(QString msg, QString type,bool isPrint,bool isSend,bool isLog,bool isProg)
{
    if(msg.isEmpty() || type.isEmpty())
        return;

    QString content = QString::asprintf("[<%s>%s %s:%d] %s",
                                        type.toStdString().c_str(),
                                        QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss").toStdString().c_str(),
                                        __FILE__,
                                        __LINE__,
                                        msg.toStdString().c_str());

    if(!isProg)
    {
        content = QString::asprintf("[<%s>%s] %s",
                                                type.toStdString().c_str(),
                                                QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss").toStdString().c_str(),
                                                msg.toStdString().c_str());
    }

    if(isPrint)
        qDebug()<<content;

    if(isLog)
    {
        if(type == "info")
            QLOG_INFO()<<content;
        else if(type == "warn")
            QLOG_WARN()<<content;
        else if(type == "debug")
            QLOG_DEBUG()<<content;
        else if(type == "error")
            QLOG_ERROR()<<content;
        else if(type == "fatal")
            QLOG_FATAL()<<content;
    }

    if(isSend)
    {
        QJsonObject mesObjReturn;
        mesObjReturn["mesid"] = IDD_JS_MESSAGE_LOG;
        mesObjReturn["type"] = type;
        mesObjReturn["content"] = content;

        m_WebSocketServer.SendAll(JsonToString(mesObjReturn));
    }
}

void CJsManager::WorkingdirectoryChanged(void)
{
    if(m_isLoadScript && !m_currentWorkingPath.isEmpty())
    {
        if(js_loadscriptstring_callback(m_currentWorkingPath+"/main.js"))
            js_setup_callback();
    }
}

/**
 * @brief CJsManager::setCurrentWorkingPath 设置当前系统工作目录
 * @param ppath 要设置的工作目录
 */
void CJsManager::setCurrentWorkingPath(QString ppath)
{
    //判断路径是否存在
    QDir dir(ppath);
    if(!dir.exists())
    {
        return;
    }

    m_currentWorkingPath = ppath;
    m_pSystemWorkingPathWatcher.addPath(ppath);
}

/**
 * @brief CJsManager::OnProcessConnectedNetMes 处理一个新的连接到达
 * @param conn 到达的新的连接
 */
void CJsManager::OnProcessConnectedNetMes(QWebSocket *conn)
{
    //m_WebSocketServer.Send(conn,"JsManager v1.0");
}

/**
 * @brief CJsManager::OnProcessDisconnectedNetMes 处理一个连接关闭
 * @param conn 要断开的连接
 */
void CJsManager::OnProcessDisconnectedNetMes(QWebSocket *conn)
{

}

/**
 * @brief CLuaManager::OnProcessNetBinary 处理网络二进制消息
 * @param conn 要处理的客户端
 * @param data 到达的二进制消息
 */
void CJsManager::OnProcessNetBinary(QWebSocket *conn,QByteArray &data)
{

}

/**
 * @brief CJsManager::OnProcessNetText 处理网络字符串消息
 * @param conn 要处理的客户端
 * @param mes 到达的字符串消息
 */
void CJsManager::OnProcessNetText(QWebSocket *conn,QString mes)
{
    QJsonObject mesObj = StringToJson(mes);

    if(m_IsEnableAuthManager)
    {
        QString authname = mesObj["authname"].toString();
        QString authpwd = mesObj["authpwd"].toString();

        QMap<QString,QString>::iterator iter = m_AuthManager.find(authname);
        if(iter == m_AuthManager.end() || iter.key() != authpwd)
        {
            QJsonObject mesObjReturn;
            mesObjReturn["mesid"] = IDD_JS_MESSAGE_AUTH_FAIL;
            m_WebSocketServer.Send(conn,JsonToString(mesObjReturn));

            return;
        }
    }

    switch (mesObj["mesid"].toInt())
    {
    case IDD_JS_MESSAGE_GETRESOURCELIST:            // 得到资源列表
    {
        onProcessNetProjectGetInfo(conn,mesObj);
    }
        break;
    case IDD_JS_MESSAGE_GETRESOURCEDATA:            // 得到指定资源的内容
    {
        onProcessNetProjectGetInfoContent(conn,mesObj);
    }
        break;
    case IDD_JS_MESSAGE_UPDATESOURCES:              // 更新资源
    {
        onProcessNetProjectUpdateResource(conn,mesObj);
    }
        break;
    default:
        break;
    }
}

/**
 * @brief CJsManager::js_loadscriptstring_callback 从指定文件中导入脚本并执行
 * @param scriptfilepath 要导入的脚本文件路径
 */
bool CJsManager::js_loadscriptstring_callback(QString scriptfilepath)
{
    if(scriptfilepath.isEmpty())
        return false;

    QFile scriptFile(scriptfilepath);
    if (!scriptFile.open(QIODevice::ReadOnly))
    {
        QString logmsg = scriptfilepath + QString::fromUtf8(" 导入失败.");
        Log(logmsg,"error");
        return false;
    }

    QTextStream stream(&scriptFile);
    QString contents = stream.readAll();
    scriptFile.close();

    QJSValue errorValue = m_JSEngine.evaluate(contents, scriptfilepath);
    if (errorValue.isError())
    {
        QString logmsg = "js_loadscriptstring_callback error:";
        logmsg += errorValue.property("name").toString() + ", "
                 + errorValue.property("message").toString()
                 + errorValue.property("lineNumber").toInt();
        Log(logmsg,"error");

        return false;
    }
    else
    {
        Log("js_loadscriptstring_callback successed.","info");
    }

    return true;
}

/**
 * @brief CJsManager::js_setup_callback 初始脚本系统
 */
void CJsManager::js_setup_callback(void)
{
    QJSValue run_fun = m_JSEngine.globalObject().property("setup");
    if(run_fun.isError())
    {
        QString logmsg = "js_setup_callback error:";
        logmsg += run_fun.property("name").toString() + ", "
                 + run_fun.property("message").toString()
                 + run_fun.property("lineNumber").toInt();
        Log(logmsg,"error");

        return;
    }

    run_fun.call();
}

/**
 * @brief CJsManager::onProcessNetProjectUpdateResource 更新脚本资源
 * @param conn
 * @param mesObj
 */
void CJsManager::onProcessNetProjectUpdateResource(QWebSocket *conn,QJsonObject &mesObj)
{
    if(m_currentWorkingPath.isEmpty())
        return;

    m_isLoadScript = false;

    QJsonObject mesObjReturn;
    mesObjReturn["mesid"] = IDD_JS_MESSAGE_UPDATESOURCES;

    QJsonArray fileArray = mesObj["files"].toArray();
    bool isResourceChanged = false;

    QJsonArray returnFileArray;

    for(int i=0;i<fileArray.size();i++)
    {
        QJsonObject fileObj = fileArray[i].toObject();

        QString filepath = fileObj["filepath"].toString();
        int operType = fileObj["opertype"].toInt();

        bool isOk = false;
        QString pRealFilePath = m_currentWorkingPath + filepath;

        QJsonObject returnFileObj;
        returnFileObj["filepath"] = filepath;
        returnFileObj["opertype"] = operType;

        switch(operType)
        {
        case IDD_RESOURCE_ADD:
        case IDD_RESOURCE_UPDATE:
        {
            QByteArray recvArray;
            recvArray.append(fileObj["content"].toString());
            QByteArray filecontent = QByteArray::fromBase64(recvArray);

            if(!filecontent.isEmpty())
            {
                if(operType == IDD_RESOURCE_ADD)
                {
                    QString tmpFileDirPath = pRealFilePath.mid(0,pRealFilePath.lastIndexOf(tr("/")));

                    QDir dir(tmpFileDirPath);
                    if(!dir.exists())
                    {
                        dir.mkpath(tmpFileDirPath);
                    }
                }

                QFile precvFile(pRealFilePath);
                if(precvFile.open(QIODevice::WriteOnly))
                {
                    precvFile.write(filecontent);
                    precvFile.close();
                    isOk = true;
                }
            }

            returnFileObj["state"] = isOk;
        }
            break;
        case IDD_RESOURCE_DELETE:
        {
            QFileInfo pFileInfo(pRealFilePath);
            if(pFileInfo.exists())
            {
                if(pFileInfo.isDir())
                {
                    isOk = removeFolderContent(pRealFilePath);
                }
                else
                {
                    isOk = QFile::remove(pRealFilePath);
                }
            }

            returnFileObj["state"] = isOk;
        }
            break;
        default:
            break;
        }

        if(isOk)
            isResourceChanged = isOk;

        returnFileArray.push_back(returnFileObj);
    }

    mesObjReturn["files"] = returnFileArray;

    m_WebSocketServer.Send(conn,JsonToString(mesObjReturn));

    m_isLoadScript = true;

    if(isResourceChanged)
        WorkingdirectoryChanged();
}

/**
 * @brief CJsManager::reg_all2jss 注册所有用到的函数
 */
void CJsManager::reg_all2jss(void)
{
    if(m_currentRegisterDllPath.isEmpty())
        return;

    QVector<QString> pfilelist;
    if(FindFile(m_currentRegisterDllPath,pfilelist) < 0)
        return;

    for(int i=0;i<pfilelist.size();i++)
    {
        QFileInfo pFileInfo(pfilelist[i]);
        if(!pFileInfo.exists() || pFileInfo.suffix().toLower() != "dll")
            continue;

        QLibrary pDllLibrary(pfilelist[i]);
        if(!pDllLibrary.load())
        {
            QLOG_ERROR() << "load dll:"<<pfilelist[i]<<" fail.";
            Log("load dll fail.");

            continue;
        }

        reg_fun reg_xxx2Jss = (reg_fun)pDllLibrary.resolve("reg_xxx2Jss");
        if(reg_xxx2Jss)
        {
            reg_xxx2Jss(this);
        }

        //pDllLibrary.unload();
    }
}

/**
 * @brief CJsManager::onProcessNetProjectGetInfoContent 得到指定资源的内容
 * @param conn
 * @param mesObj
 */
void CJsManager::onProcessNetProjectGetInfoContent(QWebSocket *conn,QJsonObject &mesObj)
{
    if(m_currentWorkingPath.isEmpty())
        return;

    QString filepath = mesObj["filepath"].toString();

    QJsonObject mesObjReturn;
    mesObjReturn["mesid"] = IDD_JS_MESSAGE_GETRESOURCEDATA;
    mesObjReturn["filepath"] = filepath;

    QFile precvFile(m_currentWorkingPath+filepath);
    if(precvFile.open(QIODevice::ReadOnly))
    {
        mesObjReturn["content"] = QString(precvFile.readAll().toBase64());

        precvFile.close();
    }

    m_WebSocketServer.Send(conn,JsonToString(mesObjReturn));
}

/**
 * @brief CJsManager::onProcessNetProjectGetInfo 得到脚本系统所使用的资源列表
 * @param conn
 * @param mesObj
 */
void CJsManager::onProcessNetProjectGetInfo(QWebSocket *conn,QJsonObject &mesObj)
{
    if(m_currentWorkingPath.isEmpty())
        return;

    QVector<QString> pfilelist;

    int filecount = FindFile(m_currentWorkingPath,pfilelist);
    if(filecount < 0)
        return;

    QJsonObject mesObjReturn;
    mesObjReturn["mesid"] = IDD_JS_MESSAGE_GETRESOURCELIST;

    QJsonArray fileArray;

    for(int i=0;i<pfilelist.size();i++)
    {
        QJsonObject fileObj;
        fileObj["name"] = pfilelist[i].mid(m_currentWorkingPath.size());

        fileArray.push_back(fileObj);
    }

    mesObjReturn["files"] = fileArray;

    m_WebSocketServer.Send(conn,JsonToString(mesObjReturn));
}

